package org.eweb4j.mvc.config;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import org.eweb4j.cache.ActionConfigBeanCache;
import org.eweb4j.cache.Props;
import org.eweb4j.cache.SingleBeanCache;
import org.eweb4j.config.LogFactory;
import org.eweb4j.mvc.action.annotation.ActionLevel;
import org.eweb4j.mvc.action.annotation.Controller;
import org.eweb4j.mvc.action.annotation.Result;
import org.eweb4j.mvc.action.annotation.ShowValMess;
import org.eweb4j.mvc.action.annotation.Singleton;
import org.eweb4j.mvc.action.annotation.ValField;
import org.eweb4j.mvc.action.annotation.ValMess;
import org.eweb4j.mvc.action.annotation.ValParam;
import org.eweb4j.mvc.action.annotation.ValParamName;
import org.eweb4j.mvc.action.annotation.Validator;
import org.eweb4j.mvc.config.bean.ActionConfigBean;
import org.eweb4j.mvc.config.bean.ResultConfigBean;
import org.eweb4j.mvc.config.bean.ValidatorConfigBean;
import org.eweb4j.mvc.config.creator.ValidatorUtil;
import org.eweb4j.util.FileUtil;
import org.eweb4j.util.ReflectUtil;
import org.eweb4j.util.StringUtil;

public class ActionAnnotationConfig {
	/**
	 * action注解
	 * 
	 * @param cb
	 * @return
	 */
	public static String readAnnotation(List<String> scanPackages) {
		String error = null;

		try {
			if (scanPackages == null)
				return error;

			for (String scanPackage : scanPackages) {
				if (scanPackage == null || scanPackage.length() == 0)
					continue;

				// 扫描jar包
				String jarPath = FileUtil.getJarPath();
				scanPackage(jarPath, scanPackage);

				String classDir = FileUtil
						.getTopClassPath(ActionAnnotationConfig.class);

				scanDir(scanPackage, classDir);

				String classDir2 = FileUtil.getClassPath("classes");

				scanDir(scanPackage, classDir2);
			}

		} catch (Exception e) {
			error = StringUtil.getExceptionString(e);
			LogFactory.getMVCLogger("ERROR").write(error);
		}

		return error;
	}

	private static void scanDir(String scanPackage, String classDir)
			throws Exception {
		File dir = null;
		if (".".equals(scanPackage)) {
			scanPackage = "";
			dir = new File(classDir);
		} else
			dir = new File(classDir + File.separator
					+ scanPackage.replace(".", File.separator));

		LogFactory.getMVCLogger("INFO").write("scan " + dir);
		// 递归文件目录
		if (dir.isDirectory())
			scanPackage(dir, scanPackage);
	}

	/**
	 * 扫描action文件
	 * 
	 * @param dir
	 * @param actionPackage
	 * @throws Exception
	 */
	private static void scanPackage(File dir, String actionPackage)
			throws Exception {
		if (!dir.isDirectory())
			return;

		File[] files = dir.listFiles();
		if (files == null || files.length == 0)
			return;
		for (File f : files) {

			if (f.isDirectory())
				if (actionPackage.length() == 0)
					scanPackage(f, f.getName());
				else
					scanPackage(f, actionPackage + "." + f.getName());

			else if (f.isFile()) {
				if (!f.getName().endsWith(".class"))
					continue;

				StringBuilder sb = new StringBuilder(actionPackage);
				int endIndex = f.getName().lastIndexOf(".");

				String clsName = sb.append(".")
						.append(f.getName().subSequence(0, endIndex))
						.toString();

				if (clsName == null || "".equals(clsName))
					continue;

				if (clsName.startsWith("."))
					clsName = clsName.substring(1);

				handleActionClass(clsName);
			}
		}
	}

	/**
	 * scan package by jars
	 * 
	 * @param jarsParentDirPath
	 * @param packageName
	 * @throws Exception
	 */
	public static void scanPackage(String jarsParentDirPath, String packageName) {
		String path = jarsParentDirPath;
		LogFactory.getMVCLogger("INFO").write("scan " + path + " for jars");
		File[] ff = new File(path).listFiles();
		if (ff == null)
			return;

		for (File f : ff) {
			ZipInputStream zin = null;
			ZipEntry entry = null;
			try {
				zin = new ZipInputStream(new FileInputStream(f));

				LogFactory.getMVCLogger("INFO").write(
						"scanning " + f.getAbsolutePath());

				while ((entry = zin.getNextEntry()) != null) {
					String entryName = entry.getName().replace('/', '.');
					if (".".equals(packageName)
							|| entryName.startsWith(packageName))
						handleActionClass(entryName.replace(".class", ""));

					zin.closeEntry();

				}
				zin.close();
			} catch (Error e) {
				continue;
			} catch (Exception e) {
				continue;
			}
		}
	}

	/**
	 * handle action class
	 * 
	 * @param clsName
	 * @throws Exception
	 */
	private static void handleActionClass(String clsName) {

		Class<?> cls = null;
		try {
			cls = Class.forName(clsName);

			if (cls == null)
				return;

			if (cls.getAnnotation(Controller.class) == null
					&& !clsName.endsWith("Controller")
					&& !clsName.endsWith("Action")
					&& !clsName.endsWith("Control"))
				return;

			Object obj = null;
			try {
				if (cls.getAnnotation(Singleton.class) != null) {
					obj = SingleBeanCache.get(cls);
					if (obj == null) {
						obj = cls.newInstance();
						SingleBeanCache.add(cls, obj);
					}
				} else
					obj = cls.newInstance();

			} catch (Exception e) {
				LogFactory.getMVCLogger("WARRING").write(
						"the action class new instance failued -> " + clsName);
			}

			ReflectUtil ru = new ReflectUtil(obj);
			Method[] ms = ru.getMethods();
			if (ms == null)
				return;

			// 扫描方法的注解信息
			for (Method m : ms) {
				if (m.getModifiers() != 1)
					continue;

				Path path = m.getAnnotation(Path.class);

				if (path == null) {
					String methodName = m.getName();
					Method getter = ru.getGetter(methodName.replace("get", ""));
					Method setter = ru.getSetter(methodName.replace("set", ""));
					// 默认下setter和getter不作为action方法
					if (getter != null || setter != null)
						continue;
				}

				readMethodAnnotation(ru, cls, m);
			}
		} catch (Error e) {
			return;
		} catch (Exception e) {
			return;
		}
	}

	/**
	 * 读取方法里的注解
	 * 
	 * @param cls
	 * @param m
	 * @throws Exception
	 */
	private static void readMethodAnnotation(ReflectUtil ru, Class<?> cls,
			Method m) {
		String methodName = m.getName();
		String fullName = cls.getName() + "." + methodName;
		LogFactory.getMVCLogger("INFO").write(
				"read action.method --> " + fullName);

		String methodReqMapVal = null;

		Path m_path = m.getAnnotation(Path.class);
		if (m_path != null) {
			methodReqMapVal = StringUtil.parsePropValue(m_path.value());
		} else if (methodName.startsWith("do")) {
			methodReqMapVal = methodName.substring("do".length());
			methodReqMapVal = StringUtil.toLowCaseFirst(methodReqMapVal);
			methodReqMapVal = StringUtil.hump2ohter(methodReqMapVal, "-");
		} else {
			String info = fullName
					+ " does not starts with 'do' so that can not be a valid action uri mapping";
			LogFactory.getMVCLogger("INFO").write(info);
			return;
		}

		final String GET = HttpMethod.GET;
		final String POST = HttpMethod.POST;
		final String PUT = HttpMethod.PUT;
		final String DELETE = HttpMethod.DELETE;

		String[] methods = new String[4];
		methods[0] = cls.getAnnotation(GET.class) != null ? GET : "";
		methods[1] = cls.getAnnotation(POST.class) != null ? POST : "";
		methods[2] = cls.getAnnotation(DELETE.class) != null ? DELETE : "";
		methods[3] = cls.getAnnotation(PUT.class) != null ? PUT : "";

		StringBuilder m_sb = new StringBuilder("");
		for (String s : methods) {
			if (m_sb.length() > 0 && s.length() > 0)
				m_sb.append("|");

			m_sb.append(s);
		}

		String controlMethod = m_sb.toString();
		ShowValMess cls_vm = cls.getAnnotation(ShowValMess.class);
		String controlShowValErr = cls_vm == null ? "alert" : cls_vm.value();

		controlShowValErr = StringUtil.parsePropValue(controlShowValErr);

		String clazzName = cls.getName();

		String reqMethod = controlMethod.length() == 0 ? GET + "|" + POST + "|"
				+ PUT + "|" + DELETE : controlMethod;// 默认四种HTTP方法都支持
		String valErrType = controlShowValErr.trim().length() == 0 ? "alert"
				: controlShowValErr;// 验证器验证信息输出方式默认”alert“

		String[] _methods = new String[4];
		_methods[0] = m.getAnnotation(GET.class) != null ? GET : "";
		_methods[1] = m.getAnnotation(POST.class) != null ? POST : "";
		_methods[2] = m.getAnnotation(DELETE.class) != null ? DELETE : "";
		_methods[3] = m.getAnnotation(PUT.class) != null ? PUT : "";

		StringBuilder _sb = new StringBuilder("");
		for (String s : _methods) {
			if (_sb.length() > 0 && s.length() > 0)
				_sb.append("|");

			_sb.append(s);
		}
		String m_reqMethod = _sb.toString();

		reqMethod = m_reqMethod.trim().length() > 0 ? m_reqMethod : reqMethod;

		ShowValMess m_vm = m.getAnnotation(ShowValMess.class);
		valErrType = m_vm == null ? "alert" : m_vm.value();

		controlShowValErr = StringUtil.parsePropValue(valErrType);

		// Action properties
		String propId = cls.getName();
		Hashtable<String, String> props = Props.getMap(propId);
		String parentPath = null;
		String path = null;
		String method = null;
		String showValMess = null;
		String retn = null;
		ResultConfigBean rcb = null;

		Path cls_path = cls.getAnnotation(Path.class);
		String controlReqMapVal = cls_path == null ? "" : cls_path.value();

		controlReqMapVal = StringUtil.parsePropValue(controlReqMapVal);

		if (controlReqMapVal.length() > 0 && !methodReqMapVal.startsWith("/"))
			controlReqMapVal = controlReqMapVal + "/";

		String actionName = controlReqMapVal + methodReqMapVal;
		if (actionName.startsWith("/"))
			actionName = actionName.substring(1);
		if (actionName.endsWith("/"))
			actionName = actionName.substring(0, actionName.length() - 1);

		if (props != null) {

			path = props.get(methodName + ".path");
			String _path = path == null ? methodReqMapVal : path;

			parentPath = props.get("path");
			String _parentPath = parentPath == null ? controlReqMapVal
					: parentPath;

			actionName = _parentPath + _path;

			method = props.get(methodName + ".method");
			reqMethod = method == null ? reqMethod : method;
			showValMess = props.get(methodName + ".showValMess");
			retn = props.get(methodName + ".return");
			rcb = null;
			if (retn != null) {
				rcb = new ResultConfigBean();
				rcb.setName("_props_");
				if (retn.startsWith("redirect:")) {
					rcb.setType(MVCConfigConstant.REDIRECT_TYPE);
					rcb.setLocation(retn.replace("redirect:", ""));
				} else if (retn.startsWith("forward:")) {
					rcb.setType(MVCConfigConstant.FORWARD_TYPE);
					rcb.setLocation(retn.replace("forward:", ""));
				} else if (retn.equalsIgnoreCase("null")
						|| retn.equalsIgnoreCase(MVCConfigConstant.AJAX_TYPE)) {
					rcb.setType(MVCConfigConstant.AJAX_TYPE);
				} else if (retn.equalsIgnoreCase("action:")) {
					rcb.setType(MVCConfigConstant.ACTION_TYPE);
					rcb.setLocation(retn.replace("action:", ""));

				} else if (retn.startsWith("out:")) {
					rcb.setType(MVCConfigConstant.OUT_TYPE);
					rcb.setLocation(retn.replace("out:", ""));
				} else if (retn.equalsIgnoreCase(MVCConfigConstant.JSON_TYPE)) {
					rcb.setType(MVCConfigConstant.JSON_TYPE);
				} else {
					rcb.setType(MVCConfigConstant.FORWARD_TYPE);
					rcb.setLocation(retn);
				}
			}
		}
		ActionConfigBean action = new ActionConfigBean();
		action.setShowValErrorType(showValMess == null ? valErrType
				: showValMess);
		action.setClazz(clazzName);
		action.setMethod(methodName);
		action.setName(actionName);
		action.setReqMethod(reqMethod);

		// 读取@ActionLevel注解
		ActionLevel actionLevel = cls.getAnnotation(ActionLevel.class);
		if (actionLevel == null)
			actionLevel = m.getAnnotation(ActionLevel.class);
		int level = 1;
		if (actionLevel != null)
			level = actionLevel.value();

		action.setLevel(String.valueOf(level));

		// 读取@Produces注解
		Produces producesAnn = m.getAnnotation(Produces.class);
		if (producesAnn != null) {
			String producesStr = StringUtil
					.parsePropValue(producesAnn.value()[0]);
			action.getProduces().add(producesStr);
		}

		// 读取@Result注解
		Result resultAnn = m.getAnnotation(Result.class);
		if (resultAnn != null)
			action.setResult(ResultAnnUtil.readResultAnn(resultAnn));

		if (rcb != null)
			action.getResult().add(rcb);

		// 读取@Validator注解
		Validator validatorAnn = m.getAnnotation(Validator.class);
		if (validatorAnn != null)
			action.setValidator(ValidatorUtil.readValidator(validatorAnn,
					m.getAnnotation(ValField.class),
					m.getAnnotation(ValMess.class),
					m.getAnnotation(ValParamName.class),
					m.getAnnotation(ValParam.class)));

		// 读取Action object 的属性 验证信息
		List<ValidatorConfigBean> vals = ValidatorUtil.readValidator(null, ru,
				null, null);
		if (vals != null)
			action.setValidator(vals);

		// Action全名，框架用，包括对“{xxx}”url参数的正则化，HttpRequestMethod
		String actionFullName = ActionUrlUtil.mathersUrlMapping(m, actionName,
				cls);
		if (actionFullName == null)
			return;

		if (actionFullName.endsWith("/")) {
			actionFullName.substring(0, actionFullName.length() - 1);
		}

		actionFullName = actionFullName + "@" + reqMethod;

		// 将读取成功的配置信息放入缓存供框架运行期使用
		ActionConfigBeanCache.add(actionFullName, action);
		ActionClassCache.add(clazzName, cls);
	}
}
